﻿using System;
using Ninject;
using System.Linq;
using Ninject.Modules;

namespace NazTek.Core
{
    /// <summary>
    /// Access point for dependency container.  It encapsulates and exposes a subset API of dependency container framework.  
    /// It's a singleton, ensuring a single container instance.
    /// </summary>
    /// <remarks>Currently using Ninject 3.2</remarks>
    public class DependencyContainer
    {
        /// <summary>
        /// Gets/sets dependency modules, each module maintains a list of interface to implementation map.
        /// </summary>
        public static DependencyModuleArray DependencyModules { get; set; }
        /// <summary>
        /// Sets a dependency module with a list of interface to implementation map.
        /// </summary>
        public static DependencyModule DependencyModule
        {
            set { DependencyModules = new DependencyModuleArray(value); }
        }
        /// <summary>
        /// Gets the single instance, creating it if necessary.  Either <see cref="DependencyModule"/> or <see cref="DependencyModules"/> 
        /// setter property must be called to set dependency map before this property may be called, or an exception will be thrown.
        /// </summary>
        /// <remarks>The operation is thread safe.</remarks>
        public static DependencyContainer Instance
        {
            get
            {
                lock (Lock)
                {
                    if (_instance != null) 
                        return _instance;
                    if (!IsContainerHydrated)
                        throw new NullReferenceException(
                            "Either of the DependencyModule or DependencyModules properties must be set before first use of this container.");
                    _instance = new DependencyContainer();
                }
                return _instance;
            }
        }
        /// <summary>
        /// Determines if the container has any map registered.
        /// </summary>
        public static bool IsContainerHydrated { get { return DependencyModules != null && DependencyModules.Length > 0; } }
        /// <summary>
        /// Gets concrete instance of a mapped interface.
        /// </summary>
        /// <typeparam name="T">Interface to fetch a concrete implementation of.</typeparam>
        /// <returns>Concrete instance of mapped interface.</returns>
        public T Get<T>()
        {
            return Core.Get<T>();
        }
        /// <summary>
        /// Disposes container and registered modules.
        /// </summary>
        ~DependencyContainer()
        {
            foreach (var m in _instance.Core.GetModules().ToList().OfType<NinjectModule>().Where(m => m != null))
                m.Dispose();
            _instance.Core.Dispose();
            _instance.Core = null;
            DependencyModules.Dispose();
            DependencyModules = null;
        }
        /// <summary>
        /// Stores the singleton instance.
        /// </summary>
        private static DependencyContainer _instance;
        /// <summary>
        /// Stores the token, used for enforcing thread safety.
        /// </summary>
        private static readonly object Lock = new object();
        /// <summary>
        /// Gets/Sets main underlying container object.
        /// </summary>
        private StandardKernel Core { get; set; }
        /// <summary>
        /// Sets main underlying container object <see cref="Core"/> with <see cref="DependencyModules">dependency modules</see>.
        /// </summary>
        private DependencyContainer()
        {
            Core = new StandardKernel(DependencyModules.ToNinjectModules());
        }
    }
}